import datetime
import os
from collections import defaultdict
from typing import *

from config import PREMIUM, ORIGIN_BALANCE, ROOT_DIR
from config import BASE_ASSET, TRADE_LOG
from decimal import Decimal
import copy
from globalVar import GlobalVar

from utils.error import FakeExchangeError
from utils.tools import NullStdout
from utils.exchange.baseExchange import BaseExchange


class FakeExchange(BaseExchange):
    """
    模拟交易所对象，同步实盘交易所对象接口
    """

    def __init__(self, uid: str, balance: Decimal = ORIGIN_BALANCE):
        super().__init__()
        self.uid: str = uid
        # 现货仓位
        self.attr_spot_balance: Dict[str, Decimal] = defaultdict(Decimal)
        # 现货成本价
        self.spot_be_price: Dict[str, Decimal] = defaultdict(Decimal)
        # 本位币起始量
        self.attr_spot_balance[BASE_ASSET] = balance
        # 合约资金
        self.attr_swap_balance: Dict[str, Dict[str, Decimal]] = {
            BASE_ASSET: defaultdict(Decimal)
        }
        # 合约仓位 结构:币种-模式-仓位
        self.attr_swap_position: Dict[str, Dict[str, dict]] = defaultdict(lambda: defaultdict(lambda: {
            'positionAmt': Decimal(0),  # 持仓量
            'breakEvenPrice': Decimal(0),  # 成本价
            'isolatedMargin': Decimal(0),  # 保证金
        }))
        # 合约仓位模式
        self.attr_margin_type: Dict[str, str] = {}
        # 是否双向持仓
        self.attr_position_side: bool = False
        # 合约杠杆
        self.attr_leverage: Dict[str, int] = {}
        # 总成交额
        self.attr_asset_quantity: Dict[str, Decimal] = defaultdict(Decimal)
        # 手续费
        self.attr_premium: Dict[str, Decimal] = defaultdict(Decimal)
        # 订单信息 [现货0 or 合约1，币种，买卖类型，数量，成交价，角色，杠杆]
        self.order_history: List[List[Any]] = []
        # 成交信息记录
        self.file_writer: Union[NullStdout, TextIO] = open(
            os.path.join(ROOT_DIR, 'locals/output', f'trade-{self.uid}.csv'),
            'w+', encoding='utf-8') if TRADE_LOG else NullStdout()
        self.file_writer.write('时间,货币类型,币种,方向,数量,价格,角色,杠杆,成本价格,盈亏\n')
        # self._loader: BaseLoader = loader_mapper[DATABASE_TYPE]()

    def _record_order_history(self, info: List[Any]):
        dt = datetime.datetime.fromtimestamp(GlobalVar.EXE_TIMESTAMP, tz=datetime.timezone.utc)
        info.insert(0, dt.strftime('%Y-%m-%d %H:%M:%S'))
        if info[-2] == 0:
            print()
        self.file_writer.write(','.join([str(x) for x in info]) + '\n')

    def _swap_liquidation(self):
        """
        爆仓检测
        :return:
        """
        for symbol, item in self.attr_swap_position.items():
            for k, pos in item.items():
                if pos['positionAmt'] == 0:
                    continue
                price = self.swap_symbol_prices[symbol]
                if (pos['positionAmt'] *
                        (price - pos['breakEvenPrice']) + pos['isolatedMargin'] <= 0):
                    quantity: Decimal = abs(pos['positionAmt'])
                    # 计算爆仓价格
                    price: Decimal = pos['isolatedMargin'] / - pos['positionAmt'] + pos['breakEvenPrice']
                    premium = (PREMIUM['swap']['taker'] +
                               PREMIUM['force']) * quantity * price
                    self.attr_swap_balance[BASE_ASSET]['maxWithdrawAmount'] -= premium
                    self.attr_swap_balance[BASE_ASSET]['balance'] -= premium + pos['isolatedMargin']

                    self.attr_asset_quantity['swap'] += quantity * price
                    self.attr_premium['swap'] += premium
                    self._record_order_history(
                        ['swap', symbol, 'force', quantity, price, 'taker',
                         self.attr_leverage[symbol],
                         pos['breakEvenPrice'], -pos['isolatedMargin']])

                    pos['positionAmt'] = Decimal(0)
                    pos['breakEvenPrice'] = Decimal(0)
                    pos['isolatedMargin'] = Decimal(0)

    def re_cal_swap_balance(self):
        """
        重新计算合约名义价值
        :return:
        """
        self.attr_swap_balance['USDT']['balance'] = Decimal(sum(
            [(item['breakEvenPrice'] - self.swap_symbol_prices[item['symbol']]) * item['positionAmt'] + item[
                'isolatedMargin']
             for item in self.swap_position()] +
            [self.swap_balance()['USDT']['maxWithdrawAmount']]))

    def _swap_funding_rate(self):
        """
        资金费率检测
        :return:
        """
        # if GlobalVar.EXE_TIMESTAMP // 3600 % 8 != 0:
        #     return
        # for item in self.swap_position():
        #     pass

    def step(self):
        self._swap_liquidation()
        self._swap_funding_rate()
        self.re_cal_swap_balance()

    def swap_position(self) -> List[Dict[str, Any]]:
        result: List[Dict[str, Any]] = []
        for item, v in self.attr_swap_position.items():
            for k, i in v.items():
                if i['positionAmt'] == 0:
                    continue
                pos = copy.deepcopy(i)
                pos['symbol'] = item
                pos['positionSide'] = k
                pos['leverage'] = self.attr_leverage[item]
                result.append(pos)

        return result

    def swap_balance(self):
        return copy.deepcopy(self.attr_swap_balance)

    def spot_balance(self):
        return copy.deepcopy(self.attr_spot_balance)

    def spot_trade(self, trades: Union[List[Dict[str, Any]], Dict[str, Any]]) -> List[Tuple[bool, Any]]:
        """
        模拟现货交易
        :param trades:
        :return:
        """
        if not isinstance(trades, list):
            trades = [trades]
        infos: List[Tuple[bool, Any]] = []
        for param in trades:
            side: str = param['side'].lower()
            symbol: str = param['symbol']
            symbol_name: str = symbol.replace(BASE_ASSET, '')
            type_: str = 'maker' if param.get('type') == 'LIMIT' else 'taker'
            price: Decimal = param['price'] if type_ != 'taker' else GlobalVar.SPOT_PRICE[symbol]
            cum_qty: Decimal = abs(price * param['quantity'])
            premium = PREMIUM['spot'][type_] * cum_qty
            profit: Decimal = Decimal(0)
            # 余额不足 跳过交易
            if side == 'buy' and self.attr_spot_balance[BASE_ASSET] < cum_qty + premium:
                infos.append((False, f'{symbol} - {side} - 买入余额不足'))
                continue
            if side == 'sell' and self.attr_spot_balance[symbol_name] < param['quantity']:
                infos.append((False, f'{symbol} - {side} - 卖出余额不足'))
                continue

            # 加仓重算成本价格
            if side == 'buy':
                self.spot_be_price[symbol_name] = (self.spot_be_price[symbol_name] * self.attr_spot_balance[
                    symbol_name] + price * param['quantity']) / (
                                                          self.attr_spot_balance[symbol_name] + param['quantity'])
            else:
                profit = (price - self.spot_be_price[symbol_name]) * param['quantity']
            # 更新仓位
            self.attr_spot_balance[BASE_ASSET] += (cum_qty if side == 'sell' else -cum_qty) - premium
            self.attr_spot_balance[symbol_name] += param['quantity'] * (1 if side == 'buy' else -1)

            self.attr_asset_quantity['spot'] += cum_qty
            self.attr_premium['spot'] += premium
            infos.append((True, {'symbol': symbol, 'quantity': param['quantity'], 'price': price, 'side': side}))
            self._record_order_history(
                ['spot', symbol, side, param['quantity'], price, type_, 1, self.spot_be_price[symbol_name], profit])
        return infos

    def _double_side(self, param: Dict[str, Any], ps_side: str) -> Tuple[bool, Any]:
        """
        合约双向仓交易
        :param param:
        :param ps_side:
        :return:
        """
        symbol: str = param['symbol']
        side: str = param['side'].lower()
        if side not in ('buy', 'sell'):
            return False, f'错误的side：{side}'
        reduce_only: bool = param.get('reduceOnly', False)
        type_: str = 'maker' if param.get('type') == 'LIMIT' else 'taker'
        price: Decimal = param['price'] if type_ != 'taker' else GlobalVar.SWAP_PRICE[symbol]
        quantity: Decimal = param['quantity']
        if quantity <= 0:
            return False, f'错误的交易量：{quantity}'
        # 当前币种杠杆
        leverage: int = self.attr_leverage.get(symbol, 5)

        profit: Decimal = Decimal(0)
        # 当前仓位，不存在则返回一个空仓位信息
        pos = self.attr_swap_position[symbol][ps_side]
        # 成本价格
        be_price: Decimal = pos['breakEvenPrice']
        # 检查杠杆变化
        if leverage != pos.get('leverage', leverage):
            raise FakeExchangeError(f'当前杠杆与历史仓位杠杆不匹配')
        # 加开仓
        if (side == 'buy') ^ (ps_side == 'SHORT'):
            cum_qty: Decimal = quantity * price
            premium: Decimal = cum_qty * PREMIUM['swap'][type_]
            # 加仓失败
            if self.attr_swap_balance[BASE_ASSET]['maxWithdrawAmount'] <= cum_qty / leverage + premium:
                return False, f'{symbol} - {side} - 加仓、开仓余额不足'
            # 重新计算平衡价格
            pos['breakEvenPrice'] = ((pos['breakEvenPrice'] * abs(pos['positionAmt']) + cum_qty) /
                                     (abs(pos['positionAmt']) + param['quantity']))
            be_price = pos['breakEvenPrice']
            pos['positionAmt'] = (param['quantity'] + abs(pos['positionAmt'])) * (1 if side == 'buy' else -1)
            # 提交保证金
            pos['isolatedMargin'] += quantity * price / leverage
            self.attr_swap_balance[BASE_ASSET]['maxWithdrawAmount'] -= (
                    cum_qty / leverage + premium)
            self.attr_swap_balance[BASE_ASSET]['balance'] -= premium
        # 减平仓
        else:
            if abs(pos['positionAmt']) < quantity and not reduce_only:
                return False, f'非只减仓交易量不得大于当前持仓'
            quantity = abs(pos['positionAmt']) if abs(pos['positionAmt']) < quantity else quantity

            profit = (price - pos['breakEvenPrice']) * quantity * (
                -1 if ps_side == 'SHORT' else 1)
            margin = pos['isolatedMargin'] * quantity / abs(pos['positionAmt'])
            profit_margin = 0 if profit + margin < 0 else profit + margin
            pos['positionAmt'] = (abs(pos['positionAmt']) - quantity) * (1 if ps_side == 'LONG' else -1)
            # 移除保证金
            pos['isolatedMargin'] -= margin
            premium = PREMIUM['swap'][type_] * quantity * price
            self.attr_swap_balance[BASE_ASSET]['maxWithdrawAmount'] += profit_margin - premium
            self.attr_swap_balance[BASE_ASSET]['balance'] += profit_margin - premium - margin

        self.attr_asset_quantity['swap'] += quantity * price
        self.attr_premium['swap'] += premium
        self._record_order_history(
            ['swap', symbol, side, quantity, price, type_, leverage, be_price, profit])

        return True, {'symbol': symbol, 'quantity': quantity,
                      'price': price, 'side': side, 'type': type_, 'cum_qty': quantity * price,
                      'leverage': leverage}

    def _single_side(self, param: Dict[str, Any]) -> Tuple[bool, Any]:
        """
        单向仓合约交易
        :param param:
        :return:
        """
        defer: Optional[Callable] = None
        symbol: str = param['symbol']
        side: str = param['side'].lower()
        if side not in ('buy', 'sell'):
            return False, f'错误的side：{side}'
        reduce_only: bool = param.get('reduceOnly', False)
        type_: str = 'maker' if param.get('type') == 'LIMIT' else 'taker'
        price: Decimal = param['price'] if type_ != 'taker' else GlobalVar.SWAP_PRICE[symbol]
        quantity: Decimal = param['quantity']
        if quantity <= 0:
            return False, f'错误的交易量：{quantity}'
        leverage: Union[int, Decimal] = self.attr_leverage.get(symbol, 5)
        profit: Decimal = Decimal(0)

        pos = self.attr_swap_position[symbol]['BOTH']
        # 成本价格
        be_price = pos['breakEvenPrice']
        # 检查杠杆变化
        if leverage != pos.get('leverage', leverage):
            raise FakeExchangeError(f'当前杠杆与历史仓位杠杆不匹配')

        # 加开仓
        if (side == 'buy' and pos['positionAmt'] >= 0) or (side == 'sell' and pos['positionAmt'] <= 0):
            cum_qty: Decimal = quantity * price
            premium: Decimal = cum_qty * PREMIUM['swap'][type_]
            # 加仓失败
            if self.attr_swap_balance[BASE_ASSET]['maxWithdrawAmount'] <= cum_qty / leverage + premium:
                return False, f'{symbol} - {side} - 加仓、开仓余额不足'
            # 重新计算平衡价格
            pos['breakEvenPrice'] = ((pos['breakEvenPrice'] * abs(pos['positionAmt']) + cum_qty) /
                                     (abs(pos['positionAmt']) + param['quantity']))
            be_price = pos['breakEvenPrice']
            pos['positionAmt'] = (param['quantity'] + abs(pos['positionAmt'])) * (1 if side == 'buy' else -1)
            # 提交保证金
            pos['isolatedMargin'] += quantity * price / leverage
            self.attr_swap_balance[BASE_ASSET]['maxWithdrawAmount'] -= (
                    cum_qty / leverage + premium)
            self.attr_swap_balance[BASE_ASSET]['balance'] -= premium
        # 减平仓
        else:
            # 非反向
            if abs(pos['positionAmt']) >= quantity or reduce_only:
                quantity = min(abs(pos['positionAmt']), quantity)
                profit = (price - pos['breakEvenPrice']) * quantity * (
                    -1 if pos['positionAmt'] < 0 else 1)
                margin = pos['isolatedMargin'] * quantity / abs(pos['positionAmt'])
                profit_margin = 0 if profit + margin < 0 else profit + margin
                pos['positionAmt'] = (abs(pos['positionAmt']) - quantity) * (1 if pos['positionAmt'] > 0 else -1)
                # 移除保证金
                pos['isolatedMargin'] -= margin
                # 平仓时重置成本价
                if abs(pos['positionAmt']) == quantity:
                    pos['breakEvenPrice'] = Decimal(0)
                premium = PREMIUM['swap'][type_] * quantity * price
                self.attr_swap_balance[BASE_ASSET]['maxWithdrawAmount'] += profit_margin - premium
                self.attr_swap_balance[BASE_ASSET]['balance'] += profit_margin - premium - margin
            else:
                _quantity = quantity - abs(pos['positionAmt'])
                quantity = abs(pos['positionAmt'])
                profit = (price - pos['breakEvenPrice']) * quantity * (
                    -1 if pos['positionAmt'] < 0 else 1)
                margin = pos['isolatedMargin']
                profit_margin = 0 if profit + margin < 0 else profit + margin
                # 重置仓位信息
                pos['positionAmt'] = Decimal(0)
                pos['isolatedMargin'] = Decimal(0)
                pos['breakEvenPrice'] = Decimal(0)
                premium = PREMIUM['swap'][type_] * quantity * price
                self.attr_swap_balance[BASE_ASSET]['maxWithdrawAmount'] += profit_margin - premium
                self.attr_swap_balance[BASE_ASSET]['balance'] += profit_margin - premium - margin
                param['quantity'] = _quantity
                # 将反向的量重做一单
                defer = lambda: self._single_side(param)

        self.attr_asset_quantity['swap'] += quantity * price
        self.attr_premium['swap'] += premium
        self._record_order_history(
            ['swap', symbol, side, quantity, price, type_, leverage, be_price, profit])
        if defer is not None:
            defer()
        return True, {'symbol': symbol, 'quantity': quantity,
                      'price': price, 'side': side, 'type': type_, 'cum_qty': quantity * price,
                      'leverage': leverage}

    def swap_trade(self, trades: Union[List[Dict[str, Any]], Dict[str, Any]]) -> List[Tuple[bool, Any]]:
        """
        模拟合约交易
        :param trades:
        :return:
        """
        if not isinstance(trades, list):
            trades = [trades]
        # 遍历交易提交
        infos: List[Tuple[bool, Any]] = []
        for param in trades:
            ps_side: str = param.get('positionSide', 'BOTH')
            if ps_side not in ('SHORT', 'LONG', 'BOTH'):
                infos.append((False, f'未知仓位方向{ps_side}'))
                continue

            if (ps_side == 'BOTH') ^ (not self.attr_position_side):
                infos.append((False, f'仓位方向不支持{ps_side}'))
                continue
            if ps_side == 'BOTH':
                infos.append(self._single_side(param))
            else:
                infos.append(self._double_side(param, ps_side))
        return infos

    def transfer(self, orders: List[Dict[str, Any]]) -> List[bool]:
        infos: List[bool] = []
        for param in orders:
            asset: str = param['asset']
            type_: str = param['type']
            amount: Decimal = param['amount']

            if amount == 0:
                infos.append(False)
                continue
            if type_ != 'MAIN_UMFUTURE' and type_ != 'UMFUTURE_MAIN':
                raise FakeExchangeError(f'不支持的划转类型{asset}')
            self.attr_spot_balance[asset] = self.attr_spot_balance[asset] + (
                -amount if type_ == 'MAIN_UMFUTURE' else amount)
            if asset in self.attr_swap_balance.keys():
                self.attr_swap_balance[asset]['balance'] += amount if type_ == 'MAIN_UMFUTURE' else -amount
                self.attr_swap_balance[asset]['maxWithdrawAmount'] += amount if type_ == 'MAIN_UMFUTURE' else -amount
            else:
                self.attr_swap_balance[asset] = {'balance': amount if type_ == 'MAIN_UMFUTURE' else -amount,
                                                 'maxWithdrawAmount': amount}
            infos.append(True)
        return infos

    def swap_leverage(self, leverage: List[int], symbol: List[str]) -> Dict[str, bool]:
        for k, v in zip(symbol, leverage):
            # TODO: 修改杠杆时检测仓位信息
            # if self.attr_leverage[k] != v and self.attr_swap_position[k].get('BOTH'):
            #     raise FakeExchangeError(f'')
            self.attr_leverage[k] = v
        return {k: True for k in symbol}

    def margin_type(self, symbol: List[str], update: str = 'ISOLATED') -> None:
        for i in symbol:
            self.attr_margin_type[i] = update

    def swap_position_side(self, update_to: bool = None) -> Optional[bool]:
        if update_to is None:
            return self.attr_position_side
        self.attr_position_side = update_to

    @property
    def spot_exchange_info(self) -> Dict[str, Any]:
        return GlobalVar.SPOT_INFO

    @property
    def swap_exchange_info(self) -> Dict[str, Any]:
        return GlobalVar.SWAP_INFO

    @property
    def spot_symbol_prices(self) -> Dict[str, Decimal]:
        return GlobalVar.SPOT_PRICE

    @property
    def swap_symbol_prices(self) -> Dict[str, Decimal]:
        return GlobalVar.SWAP_PRICE

    def account_balance(self) -> Decimal:
        result: Decimal = Decimal(0)
        # 计算现货价值
        for k, v in self.attr_spot_balance.items():
            if k == BASE_ASSET:
                result += v
            else:
                result += v * self.spot_symbol_prices[k + BASE_ASSET]
        # 计算合约可动资产价值
        for k, v in self.attr_swap_balance.items():
            if k == BASE_ASSET:
                result += v['maxWithdrawAmount']
            else:
                result += v['maxWithdrawAmount'] * self.spot_symbol_prices[k + BASE_ASSET]
        # 计算合约仓位价值
        for item in self.swap_position():
            result += item['positionAmt'] * (self.swap_symbol_prices[item['symbol']] - item['breakEvenPrice']) / \
                      item['leverage'] + item['isolatedMargin']
        return result

    def convert_asset(self, asset: List[str]):
        """
        将币种转换为BNB
        :param asset:
        :return:
        """
        pass
